/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "pin_auth_core.h"

#include <stddef.h>

#include "apdu_core_defines.h"
#include "apdu_utils.h"
#include "card_channel.h"
#include "logger.h"

#include "pin_auth_core_inner.h"

#define DEFAULT_PIN_APDU_SIZE 128

ResultCode PinAuthResponseChecker(const ResponseApdu *apdu)
{
    return DefaultResponseChecker(apdu);
}

ResultCode ProcPinAuthCmdEnrollLocal(const CardChannel *channel, uint32_t slotId, const PinDataHash *hash,
    PinDataSecret *secret)
{
    if (channel == NULL || hash == NULL || secret == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // enroll local
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_ENROLL_LOCAL, 0x00, 0x00};

    PinEnrollLocalInput input = {.slotId = slotId, .hash = *hash};
    DataRevert((uint8_t *)&input.slotId, sizeof(input.slotId));

    CommandApdu *cmd = CreateCommandApdu(&header, (const uint8_t *)&input, sizeof(input), MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }

        result = PinAuthEnrollLocalFromRspApdu(rsp, secret);
        if (result != SUCCESS) {
            LOG_ERROR("PinAuthEnrollLocalFromRspApdu failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("enroll local to slot %u %s", slotId, (result == SUCCESS) ? "succ" : "fail");
    return result;
}

ResultCode ProcPinAuthCmdAuthLocal(const CardChannel *channel, uint32_t slotId, const PinDataHash *hash,
    PinAuthResult *result)
{
    if (channel == NULL || hash == NULL || result == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // auth local
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_AUTHENTICATION_LOCAL, 0x00, 0x00};

    PinAuthLocalInput input = {.slotId = slotId, .hash = *hash};
    DataRevert((uint8_t *)&input.slotId, sizeof(input.slotId));

    CommandApdu *cmd = CreateCommandApdu(&header, (const uint8_t *)&input, sizeof(input), MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode ret = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        ret = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (ret != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }

        ret = PinAuthAuthenticationLocalFromRspApdu(rsp, result);
        if (ret != SUCCESS) {
            LOG_ERROR("PinAuthAuthenticationLocalFromRspApdu failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("auth local to slot %u %s", slotId, (ret == SUCCESS) ? "succ" : "fail");
    return ret;
}

ResultCode ProcPinAuthCmdEnrollRemote(const CardChannel *channel, uint32_t slotId, const PinDataRemoteBase *base)
{
    if (channel == NULL || base == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // enroll remote
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_ENROLL_REMOTE, 0x00, 0x00};

    PinEnrollRemoteInput input = {.slotId = slotId, .base = *base};
    DataRevert((uint8_t *)&input.slotId, sizeof(input.slotId));

    CommandApdu *cmd = CreateCommandApdu(&header, (const uint8_t *)&input, sizeof(input), MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);

    LOG_INFO("enroll remote to slot %u %s", slotId, (result == SUCCESS) ? "succ" : "fail");
    return result;
}

ResultCode ProcPinAuthCmdAuthRemotePrepare(const CardChannel *channel, uint32_t slotId, uint64_t session,
    PinDataRemoteServiceChallenge *challenge)
{
    if (channel == NULL || challenge == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // auth remote prepare
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_AUTHENTICATION_REMOTE_PREPARE, 0x00, 0x00};

    uint8_t body[MAX_APDU_DATA_SIZE] = {0};
    uint32_t size = MAX_APDU_DATA_SIZE;
    ResultCode result = ProcPinAuthCmdAuthRemotePrepareToCmdApduData(slotId, session, body, &size);
    if (result != SUCCESS) {
        LOG_ERROR("ProcPinAuthCmdAuthRemotePrepareToCmdApduData error");
        return result;
    }

    CommandApdu *cmd = CreateCommandApdu(&header, body, size, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }

        result = ProcPinAuthCmdAuthRemotePrepareFromRspApdu(rsp, challenge);
        if (result != SUCCESS) {
            LOG_ERROR("ProcPinAuthCmdAuthRemotePrepareFromRspApdu failed, ret is %u", result);
            break;
        }
    } while (0);

    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("auto remote prepare to slot %u %s", slotId, (result == SUCCESS) ? "succ" : "fail");
    return result;
}

ResultCode ProcPinAuthCmdAuthRemote(const CardChannel *channel, uint32_t slotId, uint64_t session,
    const PinDataRemoteClientProof *proof, PinAuthResult *result)
{
    if (channel == NULL || proof == NULL || result == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // auth remote process
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_AUTHENTICATION_REMOTE_PROCESS, 0x00, 0x00};

    uint8_t body[MAX_APDU_DATA_SIZE] = {0};
    uint32_t size = MAX_APDU_DATA_SIZE;
    ResultCode ret = ProcPinAuthCmdAuthRemoteProcessToCmdApduData(slotId, session, proof, body, &size);

    CommandApdu *cmd = CreateCommandApdu(&header, body, size, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResponseApdu *rsp = NULL;
    do {
        ret = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (ret != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", ret);
            break;
        }

        ret = PinAuthAuthenticationRemoteFromRspApdu(rsp, result);
        if (ret != SUCCESS) {
            LOG_ERROR("PinAuthAuthenticationRemoteFromRspApdu failed, ret is %u", ret);
            break;
        }
    } while (0);

    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);

    LOG_INFO("auto remote prepare to slot %u %s", slotId, (ret == SUCCESS) ? "succ" : "fail");
    return ret;
}

ResultCode ProcPinAuthCmdAuthRemoteAbort(const CardChannel *channel, uint32_t slotId, uint64_t session)
{
    if (channel == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // enroll remote
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_AUTHENTICATION_REMOTE_ABORT, 0x00, 0x00};

    uint8_t body[MAX_APDU_DATA_SIZE] = {0};
    uint32_t size = MAX_APDU_DATA_SIZE;
    ResultCode result = ProcPinAuthCmdAuthRemoteAbortToCmdApduData(slotId, session, body, &size);
    if (result != SUCCESS) {
        LOG_ERROR("ProcPinAuthCmdAuthRemotePrepareToCmdApduData error");
        return result;
    }
    CommandApdu *cmd = CreateCommandApdu(&header, body, size, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);

    LOG_INFO("enroll remote to slot %u %s", slotId, (result == SUCCESS) ? "succ" : "fail");
    return result;
}

ResultCode ProcPinAuthCmdSetSelfDestructEnable(const CardChannel *channel, uint32_t slotId,
    const PinDestructConfig *config, const PinDataHash *hash, PinConfigResult *configResult)
{
    if (channel == NULL || config == NULL || hash == NULL || configResult == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // config slot
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_CONFIG_SELF_DESTRUCT, 0x00, 0x00};

    uint8_t body[MAX_APDU_DATA_SIZE] = {0};
    uint32_t size = MAX_APDU_DATA_SIZE;

    ResultCode result = PinAuthCmdSetSelfDestructEnableToCmdApduData(slotId, config, hash, body, &size);
    if (result != SUCCESS) {
        LOG_ERROR("PinAuthCmdSetSelfDestructEnableToCmdApduData error = %u", result);
        return result;
    }

    CommandApdu *cmd = CreateCommandApdu(&header, body, size, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode ret = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        ret = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (ret != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }

        ret = PinAuthCmdSetSelfDestructEnableFromRspApdu(rsp, configResult);
        if (ret != SUCCESS) {
            LOG_ERROR("PinAuthCmdSetSelfDestructEnableFromRspApdu failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("set self destruct to slot %u %s, s:%u, f:%u, d:%u, p:%u", slotId, (ret == SUCCESS) ? "succ" : "fail",
        (uint32_t)configResult->status, (uint32_t)configResult->freeze.failCnt,
        (uint32_t)configResult->freeze.destructCnt, (uint32_t)configResult->freeze.punishTime);
    return ret;
}

ResultCode ProcPinAuthCmdGetNumSlots(const CardChannel *channel, uint32_t *numSlots)
{
    if (channel == NULL || numSlots == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }
    // get slot number
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_GET_SLOTS_NUM, 0x00, 0x00};

    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }

        result = PinAuthGetNumSlotsFromRspApdu(rsp, numSlots);
        if (result != SUCCESS) {
            LOG_ERROR("PinAuthGetNumSlotsFromRspApdu failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("num slot is %u", *numSlots);
    return result;
}

ResultCode ProcPinAuthCmdGetFreezeStatus(const CardChannel *channel, uint32_t slotId, PinFreezeStatus *pinFreezeStatus)
{
    if (channel == NULL || pinFreezeStatus == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // get slot freeze status
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_GET_FREEZE_STATUS, 0x00, 0x00};

    uint32_t data = slotId;
    DataRevert((uint8_t *)&data, sizeof(data));

    CommandApdu *cmd = CreateCommandApdu(&header, (uint8_t *)&data, sizeof(data), MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }

        result = PinAuthGetFreezeStatusFormFromRspApdu(rsp, 0, pinFreezeStatus);
        if (result != SUCCESS) {
            LOG_ERROR("PinAuthGetFreezeStatusFormFromRspApdu failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("slot %u freeze status is f:%u p:%u d:%u", slotId, (uint32_t)pinFreezeStatus->failCnt,
        (uint32_t)pinFreezeStatus->punishTime, pinFreezeStatus->destructCnt);
    return result;
}

ResultCode ProcPinAuthCmdSetFreezePolicy(const CardChannel *channel, uint16_t punishStartCnt, uint16_t destructMaxCnt,
    uint32_t enableDestructDefault)
{
    if (channel == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // config freeze policy
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_CONFIG_FREEZE_POLICY, 0x00, 0x00};

    uint8_t body[MAX_APDU_DATA_SIZE] = {0};
    uint32_t size = MAX_APDU_DATA_SIZE;

    ResultCode result =
        PinAuthCmdSetFreezePolicyToCmdApduData(punishStartCnt, destructMaxCnt, enableDestructDefault, body, &size);
    if (result != SUCCESS) {
        LOG_ERROR("PinAuthCmdSetFreezePolicyToCmdApduData error = %u", result);
        return result;
    }

    CommandApdu *cmd = CreateCommandApdu(&header, body, size, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode ret = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        ret = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (ret != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }
        uint8_t status = 0;
        ret = PinAuthCmdSetFreezePolicyFromRspApdu(rsp, &status);
        if (ret != SUCCESS) {
            LOG_ERROR("PinAuthCmdSetFreezePolicyFromRspApdu failed");
            break;
        }
        ret = (uint32_t)status;
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("set freeze policy p:%u, d:%u, e:%u %s", (uint32_t)punishStartCnt, (uint32_t)destructMaxCnt,
        (uint32_t)enableDestructDefault, (ret == SUCCESS) ? "succ" : "fail");
    return ret;
}

ResultCode ProcPinAuthCmdGetFreezePolicy(const CardChannel *channel, uint16_t *punishStartCnt, uint16_t *destructMaxCnt,
    uint32_t *enableDestructDefault)
{
    if (channel == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    if (punishStartCnt == NULL || destructMaxCnt == NULL || enableDestructDefault == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // config freeze policy
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_GET_FREEZE_POLICY, 0x00, 0x00};
    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode ret = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        ret = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (ret != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }

        ret = PinAuthCmdGetFreezePolicyFromRspApdu(rsp, punishStartCnt, destructMaxCnt, enableDestructDefault);
        if (ret != SUCCESS) {
            LOG_ERROR("PinAuthCmdSetFreezePolicyFromRspApdu failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);

    LOG_INFO("get freeze policy p:%u, d:%u, e:%u %s", (uint32_t)*punishStartCnt, (uint32_t)*destructMaxCnt,
        (uint32_t)*enableDestructDefault, (ret == SUCCESS) ? "succ" : "fail");
    return ret;
}

ResultCode ProcPinAuthCmdEraseSingleSlot(const CardChannel *channel, uint32_t slotId)
{
    if (channel == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }
    // erase all slot
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_GET_ERASE_SINGLE_SLOT, 0x00, 0x00};

    uint32_t data = slotId;
    DataRevert((uint8_t *)&data, sizeof(data));

    CommandApdu *cmd = CreateCommandApdu(&header, (const uint8_t *)&data, sizeof(uint32_t), MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode ret = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        ret = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (ret != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("slot is %u, ret is %u", slotId, ret);
    return ret;
}

ResultCode ProcPinAuthCmdEraseAllSlot(const CardChannel *channel)
{
    if (channel == NULL) {
        LOG_ERROR("invalid input");
        return INVALID_PARA_NULL_PTR;
    }

    // erase all slot
    ApduCommandHeader header = {CLA_DEFAULT, INS_PIN_GET_ERASE_ALL_SLOT, 0x00, 0x00};

    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode ret = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        ret = ChannelOpenTransmitCloseWithRetry(channel, PIN_AUTH_MAX_RETRY_NUM, PinAuthResponseChecker, cmd, &rsp);
        if (ret != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed");
            break;
        }
    } while (0);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    LOG_INFO("ret is %u", ret);
    return ret;
}